//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.
// 

#include "aodv_uu_stimulus.h"

Define_Module(AODVUU_STIMULUS);

void AODVUU_STIMULUS::initialize(int stage) {
    AODVUU::initialize(stage);

    if (stage==4)
    {
        // inizialize my info
        piggyback_my_info.address = getAddress();
        piggyback_my_info.c_cell = 0;
        piggyback_my_info.c_wifi = 0;
        piggyback_my_info.distance = 0;
        piggyback_my_info.energy = 0;
        piggyback_my_info.role = STEM_STUB;

        db = dynamic_cast<KnowledgeRepository *>(this->getParentModule()->getSubmodule("udpApp", 0)->getSubmodule("db"));

    }
}

void AODVUU_STIMULUS::rreq_forward(RREQ * rreq, int size, int ttl)
{
    struct in_addr dest, orig;
    int i;

    dest.s_addr = ManetAddress(IPv4Address(AODV_BROADCAST));
    orig.s_addr = rreq->orig_addr;

    /* FORWARD the RREQ if the TTL allows it. */
    DEBUG(LOG_INFO, 0, "forwarding RREQ src=%s, rreq_id=%lu",
          ip_to_str(orig), ntohl(rreq->rreq_id));

    if(!checkRREQ_stimulus(rreq->routing_stimulus)) {
        // the RREQ will not be forwarded
        EV << getManetAddress() << " I'm not able to route packets" << endl;
    }
    else {
        /* Queue the received message in the send buffer */
#ifndef OMNETPP
        rreq = (RREQ *) aodv_socket_queue_msg((AODV_msg *) rreq, size);
        rreq->hcnt++;       /* Increase hopcount to account for
                     * intermediate route */

        /* Send out on all interfaces */
        for (i = 0; i < MAX_NR_INTERFACES; i++)
        {
            if (!DEV_NR(i).enabled)
                continue;
            aodv_socket_send((AODV_msg *) rreq, dest, size, ttl, &DEV_NR(i));
        }
#else
        rreq->hcnt++;       /* Increase hopcount to account for
                     * intermediate route */
        if (this->isStaticNode())
            rreq->hopfix++;
        /* Send out on all interfaces */
        double delay = -1;
        if (par("EqualDelay"))
            delay = par("broadcastDelay");

        for (i = 0; i < MAX_NR_INTERFACES; i++)
        {
            if (!DEV_NR(i).enabled)
                continue;
            totalRreqSend++;
            RREQ * rreq_new = check_and_cast <RREQ*>(rreq->dup());
            rreq_new->ttl=ttl;
            aodv_socket_send((AODV_msg *) rreq_new, dest, size, ttl, &DEV_NR(i),delay);
        }
#endif
    }
}

bool AODVUU_STIMULUS::checkRREQ_stimulus(uint8_t stim) {
    bool stimulusResp = true;

    EV << "Checking RREQ stimulus to forward" << endl;

    if (db) {
        double stimulus = ((double) stim) / 255.0;
        stimulusResp = db->checkRREQforward(stimulus);
    }

    return stimulusResp;
}

void AODVUU_STIMULUS::aodv_socket_process_packet(AODV_msg * aodv_msg, int len,
        struct in_addr src,struct in_addr dst, int ttl,
        unsigned int ifindex) {

    if ((aodv_msg->type == AODV_RSTATUS && ttl == 0 && // ttl is decremented for ip layer before send to aodv
            dst.s_addr == ManetAddress(IPv4Address(AODV_BROADCAST))))
    {
        status_process((RSTATUS_MSG *) aodv_msg, len, ifindex);
    }
    else {
        AODVUU::aodv_socket_process_packet(aodv_msg,  len, src, dst, ttl, ifindex);
    }
}

int AODVUU_STIMULUS::startAODVUUAgent() {

    status_start();

    return AODVUU::startAODVUUAgent();
}

void AODVUU_STIMULUS::status_process(RSTATUS_MSG * status, int rstatuslen, unsigned int ifindex) {
    if (db != NULL) {
        node_state_info_t *vect_info = (node_state_info_t *)status->info_vect;

        for (int i = 0; i < status->actual_nInfo; i++) {
            db->updateFromHello(vect_info[i]);
        }
    }
}

RSTATUS_MSG *AODVUU_STIMULUS::rstatus_create( struct in_addr dest_addr, u_int32_t dest_seqno, struct in_addr orig_addr, ManetAddress my_addr) {
    RSTATUS_MSG *pktRis = new RSTATUS_MSG();
    pktRis->type = AODV_RSTATUS;

    //add the payload

    if (MAX_SIZE_INFO >= (sizeof(node_state_info_t) * (piggyback_neighbourhood_info.size() + 1))) {
        int j = 0;
        node_state_info_t *vect_info = (node_state_info_t *)pktRis->info_vect;

        memset(pktRis->info_vect, 0, MAX_SIZE_PIGGYBACK);

        EV << "NEW STATUS MESSAGE creating... " << endl;
        EV << "Address:" << piggyback_my_info.address << endl;
        EV << "Ccell:" << piggyback_my_info.c_cell << endl;
        EV << "Cwifi:" << piggyback_my_info.c_wifi << endl;
        EV << "Distance:" << piggyback_my_info.distance << endl;
        EV << "Energy:" << piggyback_my_info.energy << endl;
        EV << "nHop:" << piggyback_my_info.nhopDistance << endl;
        EV << "Role:" << piggyback_my_info.role << endl;

        vect_info[j++]= piggyback_my_info;
        for(std::list<node_state_info_t>::iterator it = piggyback_neighbourhood_info.begin();  it != piggyback_neighbourhood_info.end(); it++) {
            vect_info[j++] = *it;
        }

        pktRis->actual_nInfo = j;
    }

    EV << "NEW STATUS MESSAGE created with " << pktRis->actual_nInfo << " informations" << endl;

    return pktRis;
}

RREQ *AODVUU_STIMULUS::rreq_create(u_int8_t flags, struct in_addr dest_addr,
                      u_int32_t dest_seqno, struct in_addr orig_addr) {

    RREQ *ris = AODVUU::rreq_create(flags, dest_addr, dest_seqno, orig_addr);
    double stimulus = 0;
    int final_stim, attemptsCount;


    attemptsCount = 0;
    seek_list_t *sl = seek_list_find(dest_addr);
    if(sl != NULL) {
        attemptsCount = sl->reqs;
    }

    if (db) {
        stimulus = db->getRoutingStimulus(attemptsCount);
    }

    final_stim = stimulus * 255.0;

    ris->routing_stimulus = final_stim;

    return ris;
}

void AODVUU_STIMULUS::clearPiggyBackInfo(void) {
    piggyback_neighbourhood_info.clear();
}

void AODVUU_STIMULUS::setMyPiggyBackInfo(void *info_p) {
    node_state_info_t *info = (node_state_info_t *)info_p;
    piggyback_my_info = *info;
}

bool AODVUU_STIMULUS::addNewPiggyBackInfo(void *info_p) {
    node_state_info_t *info = (node_state_info_t *)info_p;

    if(piggyback_neighbourhood_info.size() < (OLSR_MAX_PIGGYBACK - 1)) {
        piggyback_neighbourhood_info.push_back(*info);

        return true;
    }

    return false;
}
